/*
Copyright (C) 2012 John Tsiombikas <nuclear@member.fsf.org>,
                   Ioannis Makris <imak@imak.gr>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdlib.h>
#import <Cocoa/Cocoa.h>
#include "glory.h"
#include "gloin.h"
#include "wsys.h"
#include "log.h"
#import "glview.h"
#import "deleg.h"

#define PENDING_REDISP	1
#define PENDING_CREATE	2
#define PENDING_DESTROY 4

struct wsys_window {
	int width, height;
	NSWindow *win;
	GloinGLView *view;
	NSOpenGLContext *ctx;
	GloinWinDelegate *deleg;
	unsigned int pending;
};


static void attr_to_nsattr(NSOpenGLPixelFormatAttribute *nsattr, struct gloin_attr *gattr);
static void mask_to_nsattr(NSOpenGLPixelFormatAttribute *attr, unsigned int mask);

static int quit;
static NSAutoreleasePool *global_pool;


int gloin_wsys_init(void)
{
	GloinAppDelegate *deleg;

	global_pool = [[NSAutoreleasePool alloc] init];

	[NSApplication sharedApplication];

	deleg = [[GloinAppDelegate alloc] init];
	[NSApp setDelegate: deleg];

	quit = 0;
	return 0;
}

void gloin_wsys_cleanup(void)
{
	[NSApp terminate: nil];
	[global_pool drain];
}

int gloin_wsys_set_video_mode(int xsz, int ysz, int rate)
{
	return 0;	/* TODO */
}

int gloin_wsys_get_video_mode(int *xsz, int *ysz, int *rate)
{
	return 0;	/* TODO */
}

int gloin_wsys_create_window(struct window *win, int xsz, int ysz, unsigned int mode)
{
	NSRect rect;
	NSAutoreleasePool *pool;
	NSOpenGLPixelFormat *pf;
	NSOpenGLPixelFormatAttribute nsattr[32];
	unsigned int style;
	struct wsys_window *wswin;

	if(!(wswin = malloc(sizeof *wswin))) {
		gloin_log("gloin_wsys_create_window: failed to allocate memory\n");
		return -1;
	}
	win->wsys_win = wswin;
	wswin->width = xsz;
	wswin->height = ysz;

	if(mode == GLORY_USE_ATTR) {
		attr_to_nsattr(nsattr, &gloin_attr);
	} else {
		mask_to_nsattr(nsattr, mode);
	}

	pool = [[NSAutoreleasePool alloc] init];

	rect.origin.x = rect.origin.y = 0;
	rect.size.width = xsz;
	rect.size.height = ysz;

	/* create the OpenGL view */
	pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes: nsattr] autorelease];
	wswin->view = [[GloinGLView alloc] initWithFrame: rect pixelFormat: pf];
	wswin->view->win = win;

	/* create the window and attach the OpenGL view */
	style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
		NSResizableWindowMask;

	wswin->win = [[NSWindow alloc] initWithContentRect: rect styleMask: style
		backing: NSBackingStoreBuffered defer: YES];

	wswin->deleg = [[GloinWinDelegate alloc] init];
	wswin->deleg->win = win;

	[wswin->win setDelegate: wswin->deleg];
	[wswin->win setTitle: @"Glorious Window (Cocoa)"];
	[wswin->win setReleasedWhenClosed: YES];
	[wswin->win setContentView: wswin->view];
	[wswin->win makeFirstResponder: wswin->view];
	[wswin->win makeKeyAndOrderFront: nil];

	wswin->ctx = [wswin->view openGLContext];

	[pool drain];
	return 0;
}

void gloin_wsys_destroy_window(struct window *win)
{
	struct wsys_window *wswin = win->wsys_win;

	void (*func)() = gloin_get_callback(win, GLORY_DESTROY);
	if(func) {
		gloin_set_active(win);
		func(win->id, gloin_get_callback_data(win, GLORY_DESTROY));
	}

	if(gloin_get_active() == win) {
		[NSOpenGLContext clearCurrentContext];
	}

	[wswin->ctx release];
	[wswin->win setDelegate: nil];
	[wswin->win makeFirstResponder: nil];
	[wswin->view release];
	[wswin->deleg release];
	[wswin->win close];

	free(wswin);
	win->wsys_win = 0;
}

void gloin_wsys_mark_for_destruction(struct window *win)
{
	win->wsys_win->pending |= PENDING_DESTROY;
}

void gloin_wsys_resize(struct window *win, int xsz, int ysz)
{
	/* TODO */
}

void gloin_wsys_set_size(struct window *win, int xsz, int ysz)
{
	win->wsys_win->width = xsz;
	win->wsys_win->height = ysz;
}

void gloin_wsys_get_size(struct window *win, int *xsz, int *ysz)
{
	*xsz = win->wsys_win->width;
	*ysz = win->wsys_win->height;
}

void gloin_wsys_set_active(struct window *win)
{
	if(win) {
		[win->wsys_win->ctx makeCurrentContext];
	}
}

void gloin_wsys_resize_window(struct window *win, int width, int height)
{
	NSRect frame = [win->wsys_win->win frame];
	frame.size.width = width;
	frame.size.height = height;
	[win->wsys_win->win setFrame: frame display: YES];
}

void gloin_wsys_set_title(struct window *win, const char *str)
{
	[win->wsys_win->win setTitle: [NSString stringWithUTF8String: str]];
}

void gloin_wsys_redisplay(struct window *win)
{
	win->wsys_win->pending |= PENDING_REDISP;
}

void gloin_wsys_swap_buffers(struct window *win)
{
	[win->wsys_win->ctx flushBuffer];
}

unsigned int gloin_wsys_modifiers(void)
{
	return 0;	/* TODO */
}

void gloin_wsys_set_callback_mask(struct window *win, unsigned int cbmask)
{
}

void gloin_wsys_quit(void)
{
	quit = 1;
}

int glory_process_events(void)
{
	NSAutoreleasePool *pool;
	NSRunLoop *runloop;
	NSDate *block, *nonblock, *limdate;
	int have_idle;
	struct window *win;

	/* loop through all the windows to call any pending create callbacks */
	win = gloin_winlist();
	while(win) {
		if(win->wsys_win->pending & PENDING_CREATE) {
			gloin_call_create(win);
			win->wsys_win->pending &= ~PENDING_CREATE;
		}
		win = win->next;
	}

	have_idle = gloin_have_idle();

	pool = [[NSAutoreleasePool alloc] init];
	runloop = [[NSRunLoop currentRunLoop] retain];
	block = [runloop limitDateForMode: NSDefaultRunLoopMode];
	nonblock = [[NSDate distantPast] retain];
	limdate = have_idle ? nonblock : block;

	while(!quit) {
		NSEvent *ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: limdate
			inMode: NSDefaultRunLoopMode dequeue: YES];
		if(!ev) break;

		[NSApp sendEvent: ev];
		if(limdate == block) {
			limdate = nonblock;
		}
	}

	[runloop release];
	[pool drain];

	/* destroy any windows marked for destruction */
	win = gloin_winlist();
	while(win) {
		struct window *tmp = win;
		win = win->next;

		if(tmp->wsys_win->pending & PENDING_DESTROY) {
			glory_destroy_window(tmp->id);
		}
	}

	/* call any pending redisplays */
	win = gloin_winlist();
	while(win) {
		if(win->wsys_win->pending & PENDING_REDISP) {
			void (*func)() = gloin_get_callback(win, GLORY_DISPLAY);
			if(func) {
				gloin_set_active(win);
				func(gloin_get_callback_data(win, GLORY_DISPLAY));
			}
			win->wsys_win->pending &= ~PENDING_REDISP;
		}
		win = win->next;
	}

	/* call any idle callbacks */
	if(have_idle) {
		gloin_call_idle();
	}

	return 0;
}

void gloin_wsys_event_loop(void)
{
	while(!quit) {
		if(glory_process_events() == -1) {
			break;
		}
	}
}

static void attr_to_nsattr(NSOpenGLPixelFormatAttribute *nsattr, struct gloin_attr *gattr)
{
	int i = 0;

	/* this is very important. makes pixelformat selection behave like GLX
	 * where any non-zero value will denote "choose highest possible >= x".
	 */
	nsattr[i++] = NSOpenGLPFAMaximumPolicy;

	nsattr[i++] = NSOpenGLPFAColorSize;
	nsattr[i++] = gattr->rbits + gattr->gbits + gattr->bbits;
	nsattr[i++] = NSOpenGLPFADepthSize;
	nsattr[i++] = gattr->zbits;
	nsattr[i++] = NSOpenGLPFAStencilSize;
	nsattr[i++] = gattr->sbits;

	if(gattr->doublebuf) {
		nsattr[i++] = NSOpenGLPFADoubleBuffer;
	}
	if(gattr->stereo) {
		nsattr[i++] = NSOpenGLPFAStereo;
	}
	if(gattr->msamples > 0) {
		nsattr[i++] = NSOpenGLPFAMultisample;
		nsattr[i++] = NSOpenGLPFASampleBuffers;
		nsattr[i++] = 1;
		nsattr[i++] = NSOpenGLPFASamples;
		nsattr[i++] = gattr->msamples;
	}
	nsattr[i++] = 0;
}

static void mask_to_nsattr(NSOpenGLPixelFormatAttribute *attr, unsigned int mask)
{
	int i = 0;

	attr[i++] = NSOpenGLPFAMaximumPolicy;

	attr[i++] = NSOpenGLPFAColorSize;
	attr[i++] = 1;

	if(mask & GLORY_DOUBLE) {
		attr[i++] = NSOpenGLPFADoubleBuffer;
	}
	if(mask & GLORY_DEPTH) {
		attr[i++] = NSOpenGLPFADepthSize;
		attr[i++] = 1;
	}
	if(mask & GLORY_STENCIL) {
		attr[i++] = NSOpenGLPFAStencilSize;
		attr[i++] = 1;
	}
	if(mask & GLORY_STEREO) {
		attr[i++] = NSOpenGLPFAStereo;
	}
	if(mask & GLORY_MULTISAMPLE) {
		attr[i++] = NSOpenGLPFAMultisample;
		attr[i++] = NSOpenGLPFASampleBuffers;
		attr[i++] = 1;
		attr[i++] = NSOpenGLPFASamples;
		attr[i++] = 1;
	}
	attr[i++] = 0;
}
